package gu.sql2java;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import static com.google.common.base.Preconditions.*;
/**
 * 
 * @author guyadong
 *
 * @param <L> left type
 * @param <R> right type
 */
public interface IBeanConverter<L,R> {

    /**
     * Default abstract implementation of {@link IBeanConverter}<br>
     * 
     * @author guyadong
     *
     * @param <L> left type
     * @param <R> right type
     */
    public static abstract class  AbstractHandle <L,R>implements IBeanConverter<L, R> {
        /** L type  */
        protected final Class<?> leftType;
        /** R type  */
        protected final Class<?> rightType;
        private static Class<?> getRawClass(Type type){
            if(type instanceof Class<?>){
                return (Class<?>) type;
            } else if(type instanceof ParameterizedType){
                return getRawClass(((ParameterizedType) type).getRawType());
            } else{
                throw new IllegalArgumentException("invalid type");
            }
        }
        public AbstractHandle() {
            Type superClass = getClass().getGenericSuperclass();
            this.leftType = getRawClass(((ParameterizedType) superClass).getActualTypeArguments()[0]);
            this.rightType = getRawClass(((ParameterizedType) superClass).getActualTypeArguments()[1]);
        }
        public AbstractHandle(Class<L> leftClass,Class<R> rightClass) {
            this.leftType = checkNotNull(leftClass,"leftClass is null");
            this.rightType = checkNotNull(rightClass,"rightClass is null");
        }
        /** 
         * copy right TO left, left and right must not be null
         * @param left
         * @param right
         */
        protected abstract void doFromRight(L left, R right);
        /** 
         * copy left TO right, left and right must not be null 
         * @param left
         * @param right
         */
        protected abstract void doToRight(L left, R right);
        /**
         *  Creates a new  L instance by calling constructor with an empty argument list<br>
         *  you must override the method if the L class haven't default constructor.
         * @return L instance
         */
        @SuppressWarnings("unchecked")
        protected L newInstanceL(){ return (L) newInstance(this.leftType); }
        /**
         *  Creates a new R instance by calling constructor with an empty argument list<br>
         *  you must override the method if the R class haven't default constructor.
         * @return R instance
         */
        @SuppressWarnings("unchecked")
        protected R newInstanceR(){ return (R) newInstance(this.rightType); }
        
        protected  static<T> T newInstance(Class<T>clazz){
            try {
                return (T) clazz.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        @Override
        public L fromRight(L left, R right) {
            if(null != right && null != left){
                this.doFromRight(left, right);
            }            
            return left;
        }

        @Override
        public R toRight(L left, R right) {
            if(null != left && null != right){
                this.doToRight(left, right);
            }
            return right;
        }

        @Override
        public L fromRight(R bean) {
            return null == bean? null : fromRight(newInstanceL(),bean);
        }

        @Override
        public R toRight(L bean) {
            return null == bean? null : toRight(bean,newInstanceR());
        }

        @Override
        public R[] toRight(L[] lefts, R[] rights) {
            if(null != lefts && null != rights){
                if( lefts.length != rights.length){
                    throw new IllegalArgumentException("mismatched length between left and right array");
                }
                for(int i=0;i<lefts.length;++i){
                    this.toRight(lefts[i],rights[i]);
                }
            }
            return rights;
        }

        @Override
        public L[] fromRight(L[] lefts, R[] rights) {
            if(null != rights && null != lefts){
                if( lefts.length != rights.length){
                    throw new IllegalArgumentException("mismatched length between left and right array");
                }
                for(int i=0;i<lefts.length;++i){
                    this.fromRight(lefts[i],rights[i]);
                }
            }
            return lefts;
        }
        
        @SuppressWarnings("unchecked")
        @Override
        public R[] toRight(L[] lefts) {
            R[] rights = null;
            if(null != lefts){
                rights = (R[])java.lang.reflect.Array.newInstance(rightType,lefts.length) ;
                for(int i=0;i<lefts.length;++i){
                    rights[i] = toRight(lefts[i]);
                }
            }
            return rights;
        }

        @SuppressWarnings("unchecked")
        @Override
        public L[] fromRight(R[] rights) {
            L[] lefts = null;
            if(null != rights){
                lefts = (L[])java.lang.reflect.Array.newInstance(leftType,rights.length) ;
                for(int i=0;i<rights.length;++i){
                    lefts[i] = fromRight(rights[i]);
                }
            }
            return lefts;
        }

        @Override
        public List<R> toRight(Collection<L> beans) {
            if(null==beans){
                return null;
            }
            ArrayList<R> rights = new ArrayList<R>(beans.size());
            for(L g:beans){
                rights.add(this.toRight(g));
            }
            return rights;
        }

        @Override
        public List<L> fromRight(Collection<R> beans) {
            if(null==beans){
                return null;
            }
            ArrayList<L> lefts = new ArrayList<L>(beans.size());
            for(R n:beans){
                lefts.add(this.fromRight(n));
            }
            return lefts;
        }

        @Override
        public List<R> toRight(List<L> lefts, List<R> rights) {
            if(null != lefts && null != rights){
                if( lefts.size() != rights.size()){
                    throw new IllegalArgumentException("mismatched length between left and right list");
                }
                for(int i=0;i<lefts.size();++i){
                    this.toRight(lefts.get(i),rights.get(i));
                }
            }
            return rights;
        }

        @Override
        public List<L> fromRight(List<L> lefts, List<R> rights) {
            if(null != rights && null != lefts){
                if( lefts.size() != rights.size()){
                    throw new IllegalArgumentException("mismatched length between left and right list");
                }
                for(int i=0;i<lefts.size();++i){
                    this.fromRight(lefts.get(i),rights.get(i));
                }
            }
            return lefts;
        }

        @Override
        public List<R> toRight(List<L> lefts) {
            List<R> rights = null;
            if(null != lefts ){
                rights  = new ArrayList<>();
                for(L l:lefts){
                    rights.add(toRight(l));
                }
            }
            return rights;    
        }

        @Override
        public List<L> fromRight(List<R> rights) {
            List<L> lefts = null;
            if(null != rights ){
                lefts  = new ArrayList<>();
                for(R r:rights){
                    lefts.add(fromRight(r));
                }
            }
            return lefts;            
        }
        @Override
        public<V> Map<R,V> toRightKey(Map<L,V> lmap) {
            if(null == lmap){
                return null;
            }
            HashMap<R,V> rmap = new HashMap<R,V>(16);
            for(Entry<L, V> entry:lmap.entrySet()){
                rmap.put(this.toRight(entry.getKey()),entry.getValue());
            }
            return rmap;
        }
        @Override
        public<K> Map<K,R> toRightValue(Map<K,L> lmap) {
            if(null == lmap){
                return null;
            }
            HashMap<K, R> rmap = new HashMap<K, R>(16);
            for(Entry<K, L> entry:lmap.entrySet()){
                rmap.put(entry.getKey(), this.toRight(entry.getValue()));
            }
            return rmap;
        }
        @Override
        public<V> Map<L,V> fromRightKey(Map<R,V> rmap) {
            if(null == rmap){
                return null;
            }
            HashMap<L,V> lmap = new HashMap<L,V>(16);
            for(Entry<R, V> entry:rmap.entrySet()){
                lmap.put(this.fromRight(entry.getKey()),entry.getValue());
            }
            return lmap;
        }
        @Override
        public<K> Map<K,L> fromRightValue(Map<K,R> rmap) {
            if(null == rmap){
                return null;
            }
            HashMap<K, L> lmap = new HashMap<K, L>(16);
            for(Entry<K, R> entry:rmap.entrySet()){
                lmap.put(entry.getKey(), this.fromRight(entry.getValue()));
            }
            return lmap;
        }
        @Override
        public Map<R,R> toRight(Map<L,L> lmap) {
            if(null == lmap){
                return null;
            }
            HashMap<R,R> rmap = new HashMap<R,R>(16);
            for(Entry<L, L> entry:lmap.entrySet()){
                rmap.put(this.toRight(entry.getKey()),this.toRight(entry.getValue()));
            }
            return rmap;
        }
        @Override
        public Map<L,L> fromRight(Map<R,R> rmap) {
            if(null == rmap){
                return null;
            }
            HashMap<L,L> lmap = new HashMap<L,L>(16);
            for(Entry<R, R> entry:rmap.entrySet()){
                lmap.put(this.fromRight(entry.getKey()),this.fromRight(entry.getValue()));
            }
            return lmap;
        }
    }

    /**
     * copy right TO left
     * @param left
     * @param right
     * @return left,or new instance if left is null
     */
    public L fromRight(L left, R right);
    
    /**
     * copy left TO right
     * @param left
     * @param right
     * @return right,or new instance if right is null
     */
    public R toRight(L left, R right);
    /**
     * return an new instance converted from R bean
     * @param bean
     * @return L bean
     */
    public L fromRight(R bean);
    /**
     * return an new instance converted from L bean
     * @param bean
     * @return R bean
     */
    public R toRight( L bean);
    /**
     * copy rights TO lefts
     * @param lefts
     * @param rights
     * @return lefts,or new array if lefts is null
     */
    public L[] fromRight(L[] lefts,R[] rights);
    /**
     * copy lefts TO rights
     * @param lefts
     * @param rights
     * @return rights,or new array if rights is null
     */
    public R[] toRight(L[] lefts,R[] rights);
    /**
     * return an new array converted from R beans
     * @param beans
     * @return L bean array
     */
    public L[] fromRight(R[] beans);
    /**
     * an new array converted from L beans
     * @param beans
     * @return R bean array
     */
    public R[] toRight(L[] beans);
    /**
     * copy rights TO lefts
     * @param lefts
     * @param rights
     * @return lefts,or new array if lefts is null
     */
    public List<L> fromRight(List<L> lefts,List<R> rights);
    /**
     * copy lefts TO rights
     * @param lefts
     * @param rights
     * @return rights,or new array if rights is null
     */
    public List<R> toRight(List<L> lefts,List<R> rights);
    /**
     * return an new list converted from R beans
     * @param beans
     * @return L bean list
     */
    public List<L> fromRight(List<R> beans);
    /**
     * return an new list converted from L beans
     * @param beans
     * @return R bean list
     */
    public List<R> toRight(List<L> beans);
    /**
     * return an new list converted from R beans
     * @param beans
     * @return L bean list
     */
    public List<L> fromRight(Collection<R> beans);
    /**
     * an new list converted from L beans
     * @param beans
     * @return R bean list
     */
    public List<R> toRight(Collection<L> beans);
    
    /**
     * return an new map with R key converted from map with L key
     * @param lmap
     * @return Map with R key
     */
    public <V> Map<R,V> toRightKey(Map<L,V> lmap);
    /**
     * return an new map with R value converted from map with L value
     * @param lmap
     * @return Map with R value
     */
    public <K> Map<K,R> toRightValue(Map<K,L> lmap);
    /**
     * return an new map with L key converted from map with R key
     * @param rmap
     * @return Map with L key
     */
    public <V> Map<L,V> fromRightKey(Map<R,V> rmap);
    /**
     * return an new map with L value converted from map with R value
     * @param rmap
     * @return Map with L value
     */
    public <K> Map<K,L> fromRightValue(Map<K,R> rmap);
    /**
     * an new map with R key and  R value converted from map with L key and L value
     * @param lmap
     * @return Map with R key and value
     */
    public Map<R,R> toRight(Map<L,L> lmap);
    /**
     * return an new map with L key and  L value converted from map with R key and R value
     * @param rmap
     * @return Map with L key and value
     */
    public Map<L,L> fromRight(Map<R,R> rmap);
}
